import { isObject } from "@vue/shared";
import { computed, effectScope, getCurrentInstance, inject, isRef, reactive, toRefs, watch } from "vue";
import { addSubscription, triggerSubscription } from "./pubSub";
import { activePinia, setActivePinia, SymbolPinia } from "./rootStore";


// 合并数据,递归合并
function mergeReactiveObject(target, partialState) {
  for (let key in partialState) {
    if (partialState.hasOwnProperty(key)) {
      const oldValue = target[key];
      const newValue = partialState[key];
      // 状态有可能是ref,ref是对象不能递归
      if (isObject(oldValue) && isObject(newValue) && !isRef(newValue)) {
        // 普通数组对象就接着递归
        target[key] = mergeReactiveObject(oldValue, newValue);
      } else {
        target[key] = newValue;
      }
    }
  }
  return target;
}


// idOrOptions有可能是id也有可能是options,id有可能在option.id里
export function defineStore(idOrOptions, setup) {
  // 仓库名
  let id;
  // 配置项
  let options;
  if (typeof idOrOptions === 'string') {
    id = idOrOptions;
    options = setup;
  } else {
    id = idOrOptions.id;
    options = idOrOptions;
  }
  // setup有可能是个setup,也有可能是个配置项
  const isSetupStore = typeof setup === 'function';
  // 实例store的方法,外部用useStore时走的就是这个方法
  function useStore() {
    // 确保只有在组件中才能用此方法
    // 就调getCurrentInstance获取组件实例的方法,如果有就是在组件中
    const currentInstance = getCurrentInstance();
    // 依赖注入
    let pinia = currentInstance && inject(SymbolPinia);
    // 如果有值就重新存一下
    if(pinia) setActivePinia(pinia);
    // 这样setup外也能拿到pinia了
    pinia = activePinia;
    // 先判断一下以前实没实例过这个id的store
    if (!pinia._s.has(id)) {
      // 如果没有就实例一个
      if (isSetupStore) {
        creatSetupStore(id, setup, pinia);
      } else {
        createOptionsStore(id, options, pinia);
      }
    }
    // 从pinia的map里拿到这个id的state,并返回
    const store = pinia._s.get(id);
    return store;
  }
  return useStore;
}

// 创建一个setup的store
function creatSetupStore(id, setup, pinia) {
  // 批量修改数据
  function $patch(partialStateOrMutation) {
    if (typeof partialStateOrMutation === 'function') {
      // 如果是方法
      partialStateOrMutation(store);
    } else {
      // 如果是对象,就递归合并
      mergeReactiveObject(store, partialStateOrMutation);
    }
  }
  // 当action变化时通知用户/发布订阅
  const actionSubscribes = [];
  // 为store添加一些辅助方法
  const partialStore = {
    $patch,
    // 广播数据更新了
    $subscribe(callback, options) {
      scope.run(() => watch(pinia.state.value[id], (state) => {
        callback({ type: 'dirct' }, state);
      }, options));
    },
    // 广播触发action了
    $onAction: addSubscription.bind(null, actionSubscribes),
    // 不要这个store了
    $dispose() {
      // 只能不在监听effect了
      // 改state和action一样会执行,不会被禁止
      // 停止依赖收集
      scope.stop();
      // onAction回调清空
      actionSubscribes.length = 0;
      // 删除这个store
      pinia._s.delete(id);
    }
  };
  // store是最终的仓库,所有东西直接从store身上获取
  const store = reactive(partialStore);
  // 直接替换原有属性
  Object.defineProperty(store,'$state',{
    get(){
      return pinia.state.value[id];
    },
    set(state){
      $patch(store=>{
        Object.assign(store,state);
      })
    }
  })
  let scope;
  // _e就是createPinia时候创建的
  // _e能停止所有store,但是我们还希望每个store也能停止自己的
  // 所以给每个scope也来个effectScope
  const setupStore = pinia._e.run(() => {
    scope = effectScope();
    return scope.run(() => setup());
  })
  // 包装action,触发acion时触发一些额外的逻辑
  function wrapAction(name, action) {
    return function () {
      // 发布$onAction
      const afterCallBackList = [];
      const onErrorCallBackList = [];
      // triggerSubscription执行时,如果用户写了after就会触发此方法把回调压入数组中
      function after(cb) {
        afterCallBackList.push(cb);
      }
      function onError(cb) {
        onErrorCallBackList.push(cb);
      }
      // 执行onAction的回调
      triggerSubscription(actionSubscribes, { after, onError, store, name })
      let res;
      try {
        // 稳定this指向,剩余参数传进去
        res = action.apply(store, arguments);
      } catch (err) {
        // 报错执行error回调
        triggerSubscription(onErrorCallBackList, err);
      }
      // 如果是promise就等它完事
      if (res instanceof Promise) {
        return res.then(res => {
          // 成功了执行after回调
          triggerSubscription(afterCallBackList, res);
        }).catch(err => {
          triggerSubscription(onErrorCallBackList, err);
          Promise.reject(err);
        })
      } else {
        // 成功了执行after回调
        triggerSubscription(afterCallBackList, res);
        // 把返回值正常返回
        return res;
      }

    }
  }
  for (let key in setupStore) {
    const prop = setupStore[key];
    if (typeof prop === 'function') {
      // 如果是函数就重新包装(wrap)一下这个方法,触发acion时触发一些额外的逻辑
      setupStore[key] = wrapAction(key, prop);
    }
  }
  // 合并store+setupStore
  Object.assign(store, setupStore);
  // 循环调用插件
  pinia._p.forEach(plugins=>{
    // 如果有返回值就合并在store上,方便store.XXX调用
    Object.assign(store,plugins({store,app:pinia._a,pinia}));
  })
  // 用map存起来这个store
  pinia._s.set(id, store);
  return store;
}

// 处理options转成setup
function createOptionsStore(id, options, pinia) {
  const { state, getters, actions } = options;
  // 传的不是setup就变成个setup
  function setup() {
    // 为pinia.state.id附上state,用户传state了就执行,没传就默认给空
    // pinia.state是个空ref,ref放入的是个对象会被自动代理
    pinia.state.value[id] = state ? state() : {};
    // 后面assign时候会丢失响应式,这里转成ref可以保留响应式,不然计算属性会有问题
    const localState = toRefs(pinia.state.value[id]);
    return Object.assign(
      localState,
      actions,
      // 处理getter,直接包一层计算属性
      Object.keys(getters || {}).reduce((computedGetters, getter) => {
        computedGetters[getter] = computed(() => {
          return getters[getter].call(store, store);
        });
        return computedGetters;
      }, {})
    );
  }
  // 把setup传进去生成store
  const store = creatSetupStore(id, setup, pinia);
  // 重置数据
  store.$reset = function () {
    const newState = state ? state() : {};
    store.$patch(($state) => {
      // 直接把老的覆盖
      Object.assign($state, newState);
    });
  };
  return store;
}